﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Tools.Model;

namespace Tools.DataAccess.Repository
{
    public class Repository<TEntity> : IRepository<TEntity>
        where TEntity : EntityBase
    {
        private IDbContext context;
        private IDbSet<TEntity> DbSet
        {
            get
            {
                return context.GetSet<TEntity>();
            }
        }

        public Repository(IDbContext Context)
        {
            context = Context;
        }

        //public void ApplyChanges(TEntity rootEntity)
        //{
        //    // Page 95 julie lerman Context
        //    // Concurency check for Optimistic mode should be written 
        //    // so should save Original value
        //    context.Set<TEntity>().Add(rootEntity);

        //    CheckForEntitiesWithoutStateInterface(context);

        //    foreach (var entry in context.ChangeTracker.Entries<IObjectWithState>())
        //    {
        //        IObjectWithState stateInfo = entry.Entity;
        //        entry.State = ConvertState(stateInfo.State);
        //        if (stateInfo.State == State.Unchanged)
        //        {
        //            ApplyPropertyChanges(entry.OriginalValues, stateInfo.OriginalValues);
        //        }
        //    }
        //    context.SaveChanges();

        //    /* Client sample
        //     * 
        //     * private static void TestSaveDestinationGraph()
        //        {
        //        Destination canyon;
        //        using (var context = new BreakAwayContext())
        //        {
        //        canyon = (from d in context.Destinations.Include(d => d.Lodgings)
        //        where d.Name == "Grand Canyon"
        //        select d).Single();
        //        }
        //        canyon.TravelWarnings = "Carry enough water!";
        //        canyon.State = State.Modified;
        //        var firstLodging = canyon.Lodgings.First();
        //        firstLodging.Name = "New Name Holiday Park";
        //        firstLodging.State = State.Modified;
        //        var secondLodging = canyon.Lodgings.Last();
        //        secondLodging.State = State.Deleted;
        //        canyon.Lodgings.Add(new Lodging
        //        {
        //        Name = "Big Canyon Lodge",
        //        State = State.Added
        //        });
        //        SaveDestinationGraph(canyon);
        //        }
        //     */
        //}

        //private void CheckForEntitiesWithoutStateInterface(IDbContext context)
        //{
        //    var entitiesWithoutState =
        //        from e in context.ChangeTracker.Entries()
        //        where !(e.Entity is IObjectWithState)
        //        select e;

        //    if (entitiesWithoutState.Any())
        //    {
        //        throw new NotSupportedException("All entities must implement IObjectWithState");
        //    }
        //}
        /*public static EntityState ConvertState(State state)
        {
            switch (state)
            {
                case State.Added:
                    return EntityState.Added;
                //case State.Modified:
                //    return EntityState.Modified;
                case State.Deleted:
                    return EntityState.Deleted;
                default:
                    return EntityState.Unchanged;
            }
        }

        private static void ApplyPropertyChanges(DbPropertyValues values, Dictionary<string, object> originalValues)
        {
            foreach (var originalValue in originalValues)
            {
                if (originalValue.Value is Dictionary<string, object>)
                {
                    ApplyPropertyChanges((DbPropertyValues)values[originalValue.Key], (Dictionary<string, object>)originalValue.Value);
                }
                else
                {
                    values[originalValue.Key] = originalValue.Value;
                }
            }
        }
        */


        public virtual IQueryable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            //Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            IQueryable<TEntity> query = DbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                //query = query.Include(includeProperty);
                new Exception("includeProperties is not null");
            }

            //if (orderBy != null)
            //{
            //    query = query.OrderBy( orderBy);
            //}
            return query;
        }

        public virtual TEntity SingleOrDefault(Expression<Func<TEntity, bool>> filter, string includeProperties = "")
        {
            IQueryable<TEntity> query = DbSet;
            foreach (var includeProperty in includeProperties.
                Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            query = query.Where(filter);
            return query.SingleOrDefault<TEntity>();
        }

        public virtual void Add(TEntity entity)
        {
            SetPrimaryKey(entity);
            DbSet.Add(entity);
        }

        private void SetPrimaryKey(TEntity entity)
        {
            typeof(TEntity).GetProperty("Id").GetSetMethod().Invoke(entity, new object[] { GetNewId() });
        }

        private int GetNewId()
        {
            SqlParameter tableNameParam = new SqlParameter
            {
                ParameterName = "TableName",
                Value = EntityBase.GetTableName(typeof(TEntity))
            };
            SqlParameter nextIdParam = new SqlParameter
            {
                ParameterName = "NextId",
                Value = 0,
                Direction = ParameterDirection.Output
            };
            context.ExecuteSqlCommand("exec tls.sp_IncId @TableName, @NextId out ", tableNameParam, nextIdParam);
            return (int)nextIdParam.Value;
        }

        public virtual void Delete(TEntity entity)
        {
            var entry = context.Entry(entity);
            entry.State = EntityState.Deleted;
        }

        public virtual void Update(TEntity entity)
        {
            var entry = context.Entry(entity);
            DbSet.Attach(entity);
            entry.State = EntityState.Modified;
        }

        public TEntity GetById(object Id)
        {
            return DbSet.Find(Id);
        }

        public IQueryable<TEntity> GetAll()
        {
            return DbSet.AsQueryable<TEntity>();
        }

        public bool Any(Expression<Func<TEntity, bool>> filter)
        {
            return DbSet.Any(filter);
        }
    }
}


/*

      private string _KeyProperty = "ID";

      public string KeyProperty
      {
          get
          {
              return _KeyProperty;
          }
          set
          {
              _KeyProperty = value;
          }
      }

      public C Session
      {
          get { return _ctx; }
      }



      #region IRepository<E,C> Members

      public int Save()
      {
          return _ctx.SaveChanges();
      }
      /// <summary>
      /// A generic method to return ALL the entities
      /// </summary>
      /// <param name=”entitySetName”>
      /// The EntitySet name of the entity in the model.
      /// </param>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      private ObjectQuery<E> DoQuery(string entitySetName)
      {
          return _ctx.CreateQuery<E>("[" + entitySetName + "]");
      }
      /// <summary>
      /// A generic method to return ALL the entities
      /// </summary>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      public ObjectQuery<E> DoQuery()
      {
          return _ctx.CreateQuery<E>("[" + this.GetEntitySetName(typeof(E).Name) + "]");
      }

      /// <summary>
      /// </summary>
      /// <param name=”entitySetName”>
      /// The EntitySet name of the entity in the model.
      /// </param>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      private ObjectQuery<E> DoQuery(string entitySetName, ISpecification<E> where)
      {
          return
              (ObjectQuery<E>)_ctx.CreateQuery<E>("[" + entitySetName + "]")
              .Where(where.EvalPredicate);
      }

      /// <summary>
      /// </summary>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      public ObjectQuery<E> DoQuery(ISpecification<E> where)
      {
          return
              (ObjectQuery<E>)_ctx.CreateQuery<E>("[" + this.GetEntitySetName(typeof(E).Name) + "]")
              .Where(where.EvalPredicate);
      }
      /// <summary>
      /// Query Entity with Paging 
      /// </summary>
      /// <param name="maximumRows">Max no of row to Fetch</param>
      /// <param name="startRowIndex">Start Index</param>
      /// <returns>Collection of Entities</returns>
      public ObjectQuery<E> DoQuery(int maximumRows, int startRowIndex)
      {
          return (ObjectQuery<E>)_ctx.CreateQuery<E>
      ("[" + this.GetEntitySetName(typeof(E).Name) + "]").Skip<E>(startRowIndex).Take(maximumRows);
      }
      /// <summary>
      /// Query Entity in sorted Order
      /// </summary>
      /// <param name="sortExpression">Sort Expression/condition</param>
      /// <param name="ErrorCode">custom Error Message</param> 
      /// <returns>Collection of Entities</returns>
      public ObjectQuery<E> DoQuery(Expression<Func<E, object>> sortExpression)
      {
          if (null == sortExpression)
          {
              return this.DoQuery();
          }
          return (ObjectQuery<E>)((IRepository<E>)this).DoQuery().OrderBy
                      <E, object>(sortExpression);
      }
      /// <summary>
      /// Query All Entity in sorted Order with Paging support
      /// </summary>
      /// <param name="sortExpression">Sort Expression/condition</param>
      /// <param name="maximumRows">Max no of row to Fetch</param>
      /// <param name="startRowIndex">Start Index</param>
      /// <returns>Collection Of entities</returns>
      public ObjectQuery<E> DoQuery(Expression<Func<E, object>>
          sortExpression, int maximumRows, int startRowIndex)
      {
          if (sortExpression == null)
          {
              return ((IRepository<E>)this).DoQuery(maximumRows, startRowIndex);
          }
          return (ObjectQuery<E>)((IRepository<E>)this).DoQuery
          (sortExpression).Skip<E>(startRowIndex).Take(maximumRows);
      }
      /// <summary>
      /// A generic method to return ALL the entities
      /// </summary>
      /// <param name=”entitySetName”>
      /// The EntitySet name of the entity in the model.
      /// </param>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      public IList<E> SelectAll(string entitySetName)
      {
          return DoQuery(entitySetName).ToList();
      }
      /// <summary>
      /// A generic method to return ALL the entities
      /// </summary>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      public IList<E> SelectAll()
      {
          try
          {
              return DoQuery().ToList(); //_ctx.CreateQuery<E>("[" + typeof(E).Name + "]");
          }
          catch (Exception)
          {
              throw;
          }
      }

      /// <summary>
      /// A generic method to return ALL the entities
      /// </summary>
      /// <param name=”entitySetName”>
      /// The EntitySet name of the entity in the model.
      /// </param>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      public IList<E> SelectAll(string entitySetName, ISpecification<E> where)
      {
          return DoQuery(entitySetName, where).ToList();
      }

      /// <summary>
      /// A generic method to return ALL the entities
      /// </summary>
      /// <typeparam name=”TEntity”>
      /// The Entity to load from the database.
      /// </typeparam>
      /// <returns>Returns a set of TEntity.</returns>
      public IList<E> SelectAll(ISpecification<E> where)
      {
          return DoQuery(where).ToList();
      }
      /// <summary>
      /// Select All Entity with Paging 
      /// </summary>
      /// <param name="maximumRows">Max no of row to Fetch</param>
      /// <param name="startRowIndex">Start Index</param>
      /// <returns>Collection of Entities</returns>
      public IList<E> SelectAll(int maximumRows, int startRowIndex)
      {
          return DoQuery(maximumRows, startRowIndex).ToList();
      }
      /// <summary>
      /// Select All Entity in sorted Order
      /// </summary>
      /// <param name="sortExpression">Sort Expression/condition</param>
      /// <param name="ErrorCode">custom Error Message</param> 
      /// <returns>Collection of Entities</returns>
      public IList<E> SelectAll(Expression<Func<E, object>> sortExpression)
      {
          if (null == sortExpression)
          {
              return DoQuery(sortExpression).ToList();
          }
          return DoQuery(sortExpression).ToList();
      }
      /// <summary>
      /// Select All Entity in sorted Order with Paging support
      /// </summary>
      /// <param name="sortExpression">Sort Expression/condition</param>
      /// <param name="maximumRows">Max no of row to Fetch</param>
      /// <param name="startRowIndex">Start Index</param>
      /// <returns>Collection Of entities</returns>
      public IList<E> SelectAll(Expression<Func<E, object>>
          sortExpression, int maximumRows, int startRowIndex)
      {
          if (sortExpression == null)
          {
              return DoQuery(maximumRows, startRowIndex).ToList();
          }
          return DoQuery(sortExpression, maximumRows, startRowIndex).ToList();
      }
      /// <summary>
      /// Get Entity By Primary Key
      /// </summary>
      /// <typeparam name="E">Entity Type</typeparam>
      /// <param name="Key">Primary Key Value</param>
      /// <returns>return entity</returns>
      public E SelectByKey(string Key)
      {
          // First we define the parameter that we are going to use the clause. 
          var xParam = Expression.Parameter(typeof(E), typeof(E).Name);
          MemberExpression leftExpr = MemberExpression.Property(xParam, this._KeyProperty);
          Expression rightExpr = Expression.Constant(Key);
          BinaryExpression binaryExpr = MemberExpression.Equal(leftExpr, rightExpr);
          //Create Lambda Expression for the selection 
          Expression<Func<E, bool>> lambdaExpr =
          Expression.Lambda<Func<E, bool>>(binaryExpr,
          new ParameterExpression[] { xParam });
          //Searching ....
          var resultCollection = (ObjectQuery<E>)_ctx.CreateQuery<E>("[" + this.GetEntitySetName(typeof(E).Name) + "]")
              .Where(lambdaExpr);
          if (null != resultCollection && resultCollection.Count() > 0)
          {
              //return valid single result
              return resultCollection.First<E>();
          }//end if 
          return null;
      }
      /// <summary>
      /// Check if value of specific field is already exist
      /// </summary>
      /// <typeparam name="E"></typeparam>
      /// <param name="fieldName">name of the Field</param>
      /// <param name="fieldValue">Field value</param>
      /// <param name="key">Primary key value</param>
      /// <returns>True or False</returns>
      public bool TrySameValueExist(string fieldName, object fieldValue, string key)
      {
          // First we define the parameter that we are going to use the clause. 
          var xParam = Expression.Parameter(typeof(E), typeof(E).Name);
          MemberExpression leftExprFieldCheck =
          MemberExpression.Property(xParam, fieldName);
          Expression rightExprFieldCheck = Expression.Constant(fieldValue);
          BinaryExpression binaryExprFieldCheck =
          MemberExpression.Equal(leftExprFieldCheck, rightExprFieldCheck);

          MemberExpression leftExprKeyCheck =
          MemberExpression.Property(xParam, this._KeyProperty);
          Expression rightExprKeyCheck = Expression.Constant(key);
          BinaryExpression binaryExprKeyCheck =
          MemberExpression.NotEqual(leftExprKeyCheck, rightExprKeyCheck);
          BinaryExpression finalBinaryExpr =
          Expression.And(binaryExprFieldCheck, binaryExprKeyCheck);

          //Create Lambda Expression for the selection 
          Expression<Func<E, bool>> lambdaExpr =
          Expression.Lambda<Func<E, bool>>(finalBinaryExpr,
          new ParameterExpression[] { xParam });
          //Searching ....            
          return _ctx.CreateQuery<E>("[" + this.GetEntitySetName(typeof(E).Name) + "]").Any<E>
                      (lambdaExpr);
      }
      /// <summary>
      /// Check if Entities exist with Condition
      /// </summary>
      /// <param name="selectExpression">Selection Condition</param>
      /// <returns>True or False</returns>
      public bool TryEntity(ISpecification<E> selectSpec)
      {
          return _ctx.CreateQuery<E>("[" + this.GetEntitySetName(typeof(E).Name) + "]").Any<E>
                      (selectSpec.EvalPredicate);
      }
      /// <summary>
      /// Get Count of all records
      /// </summary>
      /// <typeparam name="E"></typeparam>
      /// <returns>count of all records</returns>
      public int GetCount()
      {
          return _ctx.CreateQuery<E>("[" + this.GetEntitySetName(typeof(E).Name) + "]").Count();
      }
      /// <summary>
      /// Get count of selection
      /// </summary>
      /// <typeparam name="E">Selection Condition</typeparam>
      /// <returns>count of selection</returns>
      public int GetCount(ISpecification<E> selectSpec)
      {
          return _ctx.CreateQuery<E>("[" + this.GetEntitySetName(typeof(E).Name) + "]")
              .Where(selectSpec.EvalPredicate).Count();
      }
      /// <summary>
      /// Delete data from context
      /// </summary>
      /// <typeparam name="E"></typeparam>
      /// <param name="entity"></param>
      public void Delete(E entity)
      {
          _ctx.DeleteObject(entity);
      }
      /// <summary>
      /// Delete data from context
      /// </summary>
      /// <typeparam name="E"></typeparam>
      /// <param name="entity"></param>
      public void Delete(object entity)
      {
          _ctx.DeleteObject(entity);
      }
      /// <summary>
      /// Insert new data into context
      /// </summary>
      /// <typeparam name="E"></typeparam>
      /// <param name="entity"></param>
      public void Add(E entity)
      {
          _ctx.AddObject(this.GetEntitySetName(entity.GetType().Name), entity);
      }
      /// <summary>
      /// Insert if new otherwise attach data into context
      /// </summary>
      /// <param name="entity"></param>
      public void AddOrAttach(E entity)
      {
          // Define an ObjectStateEntry and EntityKey for the current object.
          EntityKey key;
          object originalItem;
          // Get the detached object's entity key.
          if (((IEntityWithKey)entity).EntityKey == null)
          {
              // Get the entity key of the updated object.
              key = _ctx.CreateEntityKey(this.GetEntitySetName(entity.GetType().Name), entity);
          }
          else
          {
              key = ((IEntityWithKey)entity).EntityKey;
          }
          try
          {
              // Get the original item based on the entity key from the context
              // or from the database.
              if (_ctx.TryGetObjectByKey(key, out originalItem))
              {//accept the changed property
                  if (originalItem is EntityObject &&
                      ((EntityObject)originalItem).EntityState != EntityState.Added)
                  {
                      // Call the ApplyCurrentValues method to apply changes
                      // from the updated item to the original version.
                      _ctx.ApplyCurrentValues(key.EntitySetName, entity);
                  }
              }
              else
              {//add the new entity
                  Add(entity);
              }//end else
          }
          catch (Exception ex)
          {
              throw ex;
          }
      }
      /// <summary>
      /// Delete all related entries
      /// </summary>
      /// <param name="entity"></param>        
      public void DeleteRelatedEntries(E entity)
      {
          foreach (var relatedEntity in (((IEntityWithRelationships)entity).
      RelationshipManager.GetAllRelatedEnds().SelectMany(re =>
      re.CreateSourceQuery().OfType<EntityObject>()).Distinct()).ToArray())
          {
              _ctx.DeleteObject(relatedEntity);
          }//end foreach
      }
      /// <summary>
      /// Delete all related entries
      /// </summary>
      /// <param name="entity"></param>        
      public void DeleteRelatedEntries(E entity, ObservableCollection<string>
                          keyListOfIgnoreEntites)
      {
          foreach (var relatedEntity in (((IEntityWithRelationships)entity).
          RelationshipManager.GetAllRelatedEnds().SelectMany(re =>
          re.CreateSourceQuery().OfType<EntityObject>()).Distinct()).ToArray())
          {
              PropertyInfo propInfo = relatedEntity.GetType().GetProperty
                          (this._KeyProperty);
              if (null != propInfo)
              {
                  string value = (string)propInfo.GetValue(relatedEntity, null);
                  if (!string.IsNullOrEmpty(value) &&
                      keyListOfIgnoreEntites.Contains(value))
                  {
                      continue;
                  }//end if 
              }//end if
              _ctx.DeleteObject(relatedEntity);
          }//end foreach
      }

      private string GetEntitySetName(string entityTypeName)
      {
          var container = this._ctx.MetadataWorkspace.GetEntityContainer
                  (this._ctx.DefaultContainerName, DataSpace.CSpace);

          return (from meta in container.BaseEntitySets

                  where meta.ElementType.Name == entityTypeName

                  select meta.Name).FirstOrDefault();

      }

      #endregion
       * */


/*namespace Tools.DA
{
    public class GenericRepository<TEntity, TContext> where TEntity : class
    {
        internal SchoolContext context;
        internal DbSet<TEntity> dbSet;

        public GenericRepository(SchoolContext context)
        {
            this.context = context;
            this.dbSet = context.Set<TEntity>();
        }

        public virtual IEnumerable<TEntity> Get(
            Expression<Func<TEntity, bool>> filter = null,
            Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
            string includeProperties = "")
        {
            IQueryable<TEntity> query = dbSet;

            if (filter != null)
            {
                query = query.Where(filter);
            }

            foreach (var includeProperty in includeProperties.Split
                (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
            {
                query = query.Include(includeProperty);
            }

            if (orderBy != null)
            {
                return orderBy(query).ToList();
            }
            else
            {
                return query.ToList();
            }
        }

        public virtual TEntity GetByID(object id)
        {
            return dbSet.Find(id);
        }

        public virtual void Insert(TEntity entity)
        {
            dbSet.Add(entity);
        }

        public virtual void Delete(object id)
        {
            TEntity entityToDelete = dbSet.Find(id);
            Delete(entityToDelete);
        }

        public virtual void Delete(TEntity entityToDelete)
        {
            if (context.Entry(entityToDelete).State == EntityState.Detached)
            {
                dbSet.Attach(entityToDelete);
            }
            dbSet.Remove(entityToDelete);
        }

        public virtual void Update(TEntity entityToUpdate)
        {
            dbSet.Attach(entityToUpdate);
            context.Entry(entityToUpdate).State = EntityState.Modified;
        }
    }
}
*/